package com.leyou.search.service;

import com.leyou.common.dto.PageDTO;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.entity.Sku;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 虎哥
 */
@Service
public class SearchService {

    private final ItemClient itemClient;

    private final ElasticsearchTemplate template;

    public SearchService(ItemClient itemClient, ElasticsearchTemplate template, GoodsRepository goodsRepository) {
        this.itemClient = itemClient;
        this.template = template;
        this.goodsRepository = goodsRepository;
    }

    public Goods buildGoods(SpuDTO spu) {
        // 1.准备ALL字段，需要拼接商品名称、分类、品牌、甚至规格。。。
        String all = spu.getName() + spu.getCategoryName() + spu.getBrandName();

        // 2.准备SKU数据
        List<SkuDTO> skuDTOList = spu.getSkus();
        if (CollectionUtils.isEmpty(skuDTOList)) {
            // 说明sku没有，我们自己去查询
            skuDTOList = itemClient.querySkuBySpuId(spu.getId());
        }
        // 处理sku
        List<Sku> skuList = skuDTOList.stream().map(Sku::new).collect(Collectors.toList());

        // 3.准备价格prices
        Set<Long> prices = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 4.准备Specs的规格参数， key是规格参数的名称，value是规格参数的值
        Map<String, Object> specs = new HashMap<>();
        // 4.1.准备KEY，是这个spu所属的分类下的规格参数，并且是要参与搜索的（searching为true）
        List<SpecParamDTO> params = itemClient.queryParams(null, spu.getCid3(), true);
        // 4.2.准备Value，是这个spu的规格参数值，存在于spu_detail中
        SpuDetailDTO spuDetail = spu.getSpuDetail();
        if (spuDetail == null) {
            spuDetail = itemClient.queryDetailBySpuId(spu.getId());
        }
        // 4.2.1.取出通用的规格参数
        String genericSpec = spuDetail.getGenericSpec();
        // 4.2.2.取出特殊的规格参数
        String specialSpec = spuDetail.getSpecialSpec();
        // 4.2.3.合并规格参数
        String specJson = genericSpec.substring(0, genericSpec.length() - 1) + "," + specialSpec.substring(1);
        Map<Long, Object> specValues = JsonUtils.toMap(specJson, Long.class, Object.class);
        if(CollectionUtils.isEmpty(specValues)){
            throw new LyException(400, "数据转换出错，规格参数为空！");
        }
        // 4.3.把key和value配对
        for (SpecParamDTO param : params) {
            // 取出参数名称，作为specs的key
            String key = param.getName();
            // 取出value，以参数的id为key取出对应的value
            Object value = specValues.get(param.getId());

            // 如果value是数字，先判断value在哪个区间范围内，然后存入索引库的是这个区间范围的字符串
            if(param.getNumeric()){
                // 是数字，处理value
                value = chooseSegment(value, param);
            }
            // 存入specs
            specs.put(key, value);
        }
        // 封装Goods
        Goods goods = new Goods();
        goods.setCreateTime(new Date());
        goods.setSubTitle(spu.getSubTitle());
        goods.setSpecs(specs);
        goods.setSkus(JsonUtils.toJson(skuList));
        goods.setPrice(prices);
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setAll(all);
        goods.setId(spu.getId());

        return goods;
    }

    private Object chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = 0;
        try {
            val = parseDouble(value.toString());
        } catch (Exception e) {
            return value;
        }
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        return Double.parseDouble(str);
    }

    public PageDTO<GoodsDTO> searchGoods(SearchRequest request) {
        // 1.准备原生搜索的构建器（工厂，构建查询条件）
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 2.构建条件
        // 2.1.source过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, new String[0]));
        // 2.2.查询条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 2.3.分页条件
        int page = request.getPage() - 1;
        queryBuilder.withPageable(PageRequest.of(page, request.getSize()));
        //TODO 2.4.排序


        // 3.查询结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        List<Goods> goodsList = result.getContent();
        List<GoodsDTO> items = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        return new PageDTO<>(result.getTotalElements(), result.getTotalPages(), items);
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {
        String key = request.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(404, "查询的数据不存在！");
        }
        // 创建一个布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 添加must，关键字查询
        queryBuilder.must(QueryBuilders.matchQuery("all", key).operator(Operator.AND).fuzziness(1));
        // 添加filter，过滤项查询  {"品牌":18374,"CPU频率":"1.5-2.0GHz"}
        Map<String, String> map = request.getFilter();
        if(!CollectionUtils.isEmpty(map)){
            // 遍历filter，取出每一个过滤项的键值对
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String name = entry.getKey();
                if("品牌".equals(name)){
                    name = "brandId";
                }else if("分类".equals(name)){
                    name = "categoryId";
                } else {
                    name = "specs." + name;
                }
                queryBuilder.filter(QueryBuilders.termQuery(name, entry.getValue()));
            }
        }
        return queryBuilder;
    }

    public Map<String, List<?>> getFilters(SearchRequest request) {
        Map<String, List<?>> filterMap = new LinkedHashMap<>();
        // 1.准备原生搜索的构建器（工厂，构建查询条件）
        NativeSearchQueryBuilder queryBuilder = getNativeSearchQueryBuilder(request);

        // 2.4.添加聚合条件
        // 2.4.1.添加分类的聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryId"));
        // 2.4.2.添加品牌的聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));

        // 3.查询结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        Aggregations aggregations = result.getAggregations();

        // 4.1.解析商品分类
        // a.根据聚合名称，获取聚合结果，这里是Term聚合，得到的就是Terms结果
        Terms categoryAgg = aggregations.get("categoryAgg");
        // b.从聚合结果中得到id集合
        List<Long> categoryIdList = getIdListFromBuckets(categoryAgg);
        // c.根据id的集合，查询分类集合
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(categoryIdList);
        // d.把分类数据放到过滤项集合结果中：
        filterMap.put("分类", categoryDTOList);

        // 4.2.解析商品品牌
        // a.根据聚合名称，获取聚合结果，这里是Term聚合，得到的就是Terms结果
        Terms brandAgg = aggregations.get("brandAgg");
        // b.从聚合结果中得到id集合
        List<Long> brandIdList = getIdListFromBuckets(brandAgg);
        // c.根据品牌id的集合，查询品牌集合
        List<BrandDTO> brandDTOList = itemClient.queryBrandByIds(brandIdList);
        // d.把品牌数据放到过滤项集合结果中：
        filterMap.put("品牌", brandDTOList);

        // 5.判断是否需要对规格参数做聚合
        if(categoryIdList.size() == 1){
            // 分类只有一个，可以做聚合
            handleSpecAgg(categoryIdList.get(0), request, filterMap);
        }
        // 6.封装要返回的结果
        return filterMap;
    }

    private void handleSpecAgg(Long cid, SearchRequest request, Map<String, List<?>> filterMap) {
        // 1.根据分类查询规格参数（当前分类下，需要搜索的）
        List<SpecParamDTO> params = itemClient.queryParams(null, cid, true);

        NativeSearchQueryBuilder queryBuilder = getNativeSearchQueryBuilder(request);
        // 2.4.添加聚合条件，对params中的每个规格参数聚合
        for (SpecParamDTO param : params) {
            // 取出param的name作为聚合名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }


        // 3.查询结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        Aggregations aggregations = result.getAggregations();

        // 需要对params中的每个规格参数都解析出聚合结果
        for (SpecParamDTO param : params) {
            // 取出param的name作为聚合名称，取出聚合结果
            String name = param.getName();
            // a.根据聚合名称，获取聚合结果，这里是Term聚合，得到的就是Terms结果
            Terms agg = aggregations.get(name);
            // b.从聚合结果获取buckets，获取buckets中的key的集合
            List<String> values = agg.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            // c.把分类数据放到过滤项集合结果中：
            filterMap.put(name, values);
        }

    }

    private NativeSearchQueryBuilder getNativeSearchQueryBuilder(SearchRequest request) {
        // 2.构建条件，准备原生搜索的构建器（工厂，构建查询条件）
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.1.source过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], new String[0]));
        // 2.2.查询条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 2.3.分页条件,这里设置成0，不需要商品数据，我们只要聚合结果
        queryBuilder.withPageable(PageRequest.of(0, 1));
        return queryBuilder;
    }

    private List<Long> getIdListFromBuckets(Terms brandAgg) {
        return brandAgg.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        /*// b.从结果中拿到Buckets
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        // c.从buckets中获取key
        List<Long> brandIdList = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            Number idNumber = bucket.getKeyAsNumber();
            long id = idNumber.longValue();
            brandIdList.add(id);
        }
        return brandIdList;
        */
    }

    private final GoodsRepository goodsRepository;

    public void saveGoodsById(Long spuId) {
        // 查询spu
        SpuDTO spu = itemClient.querySpuById(spuId);
        // 构建一个goods
        Goods goods = buildGoods(spu);
        // 新增到es
        goodsRepository.save(goods);
    }

    public void deleteGoodsById(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
